// function type expressions
function greeter(fn:(a:string) => void) {
    fn("Hello world");
}

function printToConsole(s:string) {
    console.log(s);
}

greeter(printToConsole);

// (a:string) => void 一个带参数a类型是string无返回值的函数，像声明函数没有指定类型就是any
// (string) => void 参数就是string 类型是any

type GreetFunction = (a:string) => void;
function greeter1(fn: GreetFunction) {
    //
}

// Call Signatures 回调符号
type DescribableFunction = {
    description: string;
    // 是用:而不是=>
    (someArg: number): boolean;
}

function doSomething(fn:DescribableFunction) {
    console.log(fn.description + " returned " + fn(6));
}
function myFunc(someArgs:number) {
    return someArgs > 3;
}

myFunc.description = "default description";

doSomething(myFunc);

type SomeObject = any;
// Construct Signatures
type SomeConstructor = {
    new (s:string):SomeObject;
}

function fn(ctor:SomeConstructor) {
    return new ctor("hello");
}

interface CallOrConstruct {
    new (s:string) : Date;
    (n?:number):string;
}

// Generic functions
function firstElement(arr:any[]) {
    return arr[0];
}

function firstElement1<Type>(arr: Type[]): Type | undefined {
    return arr[0];
}
// s is of type 'string'
const s = firstElement1(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement1([1, 2, 3]);
// u is of type 'undefined'
const u = firstElement1([]);

// Inference 推理
function map<Input, Output>(arr:Input[], func:(arg:Input) => Output):Output[] {
    return arr.map(func);
}
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n)=>parseInt(n));
// Constraints 约束
function longest<Type extends { length:number }>(a:Type, b:Type) {
    if(a.length > b.length) {
        return a;
    } else {
        return b;
    }
}

const longerArray = longest([1, 2], [1, 2, 3]);
const longerString = longest("alicce", "bob");
// Argument of type 'number' is not assignable to parameter of type 
// '{ length: number; }'
// const notOK = longest(10, 1000);

function minimumLength<Type extends {length : number}> (
    obj: Type, 
    minimum:number
):Type {
    if(obj.length >= minimum) {
        return obj;
    } else {
        return {length: minimum};
         /**
     * Type '{ length: number; }' is not assignable to type 'Type'.
  '{ length: number; }' is assignable to the constraint of type 'Type',
   but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
     */
    }
}

// 'arr' gets value {length: 6}
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays hava
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));

// Specifying Type Arguments
function combine<Type>(arr1:Type[], arr2:Type[]):Type[] {
    return arr1.concat(arr2);
}
const arr1 = combine([1, 2, 3], "hello");

const arr2 = combine<string | number> ([1, 2, 3], ["hello"]);

function firstElement2<Type>(arr:Type[]) {
    return arr[0];
}

function firstElement3<Type extends any[]>(arr:Type) {
    return arr[0];
}

const a = firstElement2([1, 2, 3]);
const b = firstElement3([1, 2, 3]);

function filter1<Type>(arr: Type[], func:(arg:Type) => boolean): Type[] {
    return arr.filter(func);
}

function filter2<Type, Func extends (args:Type) => boolean>(
    arr:Type[],
    func:Func
): Type[] {
    return arr.filter(func);
}

function greet<Str extends string>(s:Str) {
    console.log("Hello, " + s);
}

greet("world");
function greet1(s: string) {
    console.log("Hello, " + s);
}
// Optional Parameters
function f(n:number) {
    console.log(n.toFixed());  // 0 arguments
    console.log(n.toFixed(3)); // 1 arguments
}

function f1(n?: number) {
    // ...
}

f1();
f1(10);

function f2(x = 10) {
    // ...
}

f2();
f2(100);

declare function f3(x?: number): void;
f3();
f3(100);
f3(undefined);

function myForEach(arr:any[], callback:(arg:any, index?: number)=>void) {
    for (let i = 0; i < arr.length; i++) {
        callback(arr[i], i);
    }
}

myForEach([1,2,3],(a)=>console.log(a));
myForEach([1,2,3],(a, i)=>console.log(a, i));

myForEach([1,2,3],(a, i)=>{
    // 'i' is possibly 'undefined'
    console.log(i?.toFixed())
});
// function overloads
// overload signatures.
function makeDate(timestamp: number): Date;
// overload signatures.
function makeDate(m: number, d: number, y: number): Date;
// implementation signatures 兼容符号，不能直接调用实现，这就是为啥不能传两个参数的原因
function makeDate(mOrTimestampm: number, d?: number, y?: number): Date {
    if(d !== undefined && y !== undefined) {
        return new Date(y, mOrTimestampm, d);
    } else {
        return new Date(mOrTimestampm);
    }
}

const d1 = makeDate(123456);
const d2 = makeDate(5, 6, 7);
// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 
// const d3 = makeDate(1,3);
function fn2(x: string) : void;
// 这是实现
function fn2() {

}
// Expected 1 arguments, but got 0. 不能直接调用实现，是实现看不到外面
// fn2();
function fn3(x: boolean): void;
// This overload signature is not compatible with its implementation signature. 
// function fn3(x: string):void;
function fn3(x: boolean) {

}

function fn4(x: string): string;
// This overload signature is not compatible with its implementation signature.
// implementation signature input arg is compatible.and retrun also need compatible
// implementation signature return type is string|boolean
// function fn4(x: number): boolean;
function fn4(x: string|number) {
    return "ops";
}

function len(s: string):number;
function len(arr: any[]):number;

function len(x:any) {
    return x.length;
}

len("");
len([0]);
/**
 * No overload matches this call.
  Overload 1 of 2, '(s: string): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.
      Type 'number[]' is not assignable to type 'string'.
  Overload 2 of 2, '(arr: any[]): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.
      Type 'string' is not assignable to type 'any[]'
 */
// len(Math.random() > 0.5 ? "hello": [0]);
// because both overloads have the same argument cout and same return tpye, we can instead write a non-overloaded
// version of funciton
// function len( x: any | string) {
//     return x.length;
// }


// Declaring this in function
const user = {
    id: 123,

    admin: false,

    becomeAdmin: function {
        this.admin = true;
    }
};

interface User {
    id:number;
    admin: boolean;
}

declare const getDB: () => DB;
interface DB {
    filterUsers(filter:( this:User) => boolean) : User[]
}

const db = getDB();
const admins = db.filterUsers(function(this:User) {
    return this.admin
})
// The containing arrow function captures the global value of 'this'.
// Element implicitly has an 'any' type 
// because type 'typeof globalThis' has no index signature.
const admins1 = db.filterUsers(()=>this.admin);

// Other type to know About
// void 
// the inferred return type is void, void is not the same as undefined
function noop() {
    return;
}

// object
// the special type object refers to any value that isn't a primitive(string,
// number, bigint, boolean, sysmbol, null or undefined), This is different from
// the empty object type {}, and also different from the global type Object,
// It's very likely you will never use Object


// unkown
// unkown type represents any value, but is safer because it's not legal to do
// anything with an unknown value;
function f4(a: any) {
    a.b()// ok
}
function f5(a: unknown) {
    // 'a' is of type 'unknown'
    // a.b()// ok
}
//
function safeParse(s:string): unknown {
    return JSON.parse(s);
}

// never
// some function never return a value
// the never type represents values which are never observed.
function fail(msg:string):never {
    throw new Error(msg);
} 

function fn5(x: string | number) {
    if (typeof x === "string") {

    } else if (typeof x === "number") {

    } else {
        //(parameter) x: never
        x;
        x = 10;
    }
}


 // Function
 // the global type Function describes properites like bind, call, apply, and
 // 
 function doSomething1(f:Function) {
    return f(1,24)
 }
// rest paramters and arguments
function multiply(n:number, ...m: number[]) {
    return m.map((x)=> n * x);
}
// 1,2,3 都乘10
const a1 = multiply(10, 1 , 2, 3);
const arr3 = [1, 2, 3];
const arr4 = [1, 2, 3];
arr3.push(...arr4);

// Paramters Destructuring
function sum({a, b, c}) {
    console.log(a + b + c);
}

function sum1({a, b, c}:{a:number, b:number, c:number}) {
    console.log(a + b + c);
}
type ABC = {a:number, b:number, c:number};
function sum2({a, b, c}:ABC) {
    console.log(a + b + c);
}



sum({a: 10, b: 10, c: 10});
// Assignability of Functions
// return typr void
type voidFunc = () => void;
const f6: voidFunc = () => {
    return true;
};

const f7:voidFunc = () => true;
const f8:voidFunc = function () {
    return true;
};

const v1 = f6();
const v2 = f7();
const v3 = f8();

const src = [1,2,3];
const dst = [0];

src.forEach((el) => dst.push(el))